﻿#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
//拷贝构造，在上个程序（运算符重载）末尾的引子中也进行了相关介绍，当某个函数需要用到自定义类型的拷贝/副本时
//函数若为传值传参或返回类型为自定义类型，会自动调用拷贝构造函数进行拷贝
//拷贝构造函数其实是一种特殊的构造函数（即构造函数的重载），只不过它的第一个参数一定要是 引用类 类型
//（即 T& 类型，且多数情况下会加const，即const T&)
// 
//注意：自己写了拷贝构造后就不会默认生成了，因此，要想将成员a的内容完全拷贝给b，
//就需要在自己的拷贝构造中显式拷贝类中的所有成员，若只对需要深拷贝的变量进行拷贝，则不会对其他未被显式拷贝的变量进行操作
//->结论：需要构建拷贝构造时，始终在拷贝构造函数中显式列出所有需要拷贝的成员​​


/*
* 拷贝构造函数（Copy Constructor）
* --------------------------------
* 1. 作用：
* -当需要创建对象的副本时（如传值传参、返回值、初始化同类对象），自动调用。
* -默认生成的拷贝构造执行浅拷贝（按字节复制），可能导致指针共享等问题。
*
*2. 声明规则：
* -必须是构造函数的重载形式。
* -参数必须是同类对象的引用（通常为 `const T& ` 或 `T& `），禁止传值调用（否则无限递归）。
*
*3. 深拷贝 vs 浅拷贝：
* -浅拷贝：直接复制成员的值（包括指针地址），适合无动态资源的类。
* -深拷贝：为指针成员分配新内存并复制内容，适合管理动态资源（如堆内存）。
*
*4. 编译器行为：
* -若未显式定义拷贝构造，编译器会生成默认的浅拷贝版本（会自动拷贝内置类型成员，对于自定义类型成员，会自动调用其对应的拷贝构造函数）。
* -若类需要深拷贝（如含 `int*`），必须手动实现拷贝构造。
*
*5. 使用场景优化：
* -返回局部对象时，编译器可能优化（RVO / NRVO）跳过拷贝构造。
* -返回静态 / 堆区对象时，建议返回 `const T & ` 或 `T& ` 避免额外拷贝。
*/

//综上可以发现，在不需要开辟空间的场所，可以直接使用编译器自动生成的拷贝构造函数，而需要开辟空间时，就需要自己来创建了


////内置类型使用默认拷贝构造示例：
//class Data
//{
//public:
//	Data(int year = 2025, int month = 7, int day = 9)//创建默认构造函数与拷贝构造函数做对比
//	{
//		_year = year;
//		_month = month;
//		_day = day;
//	}
//	//由于没有开辟空间的变量，也就不需要创建析构函数和拷贝构造函数了
//	void Print()
//	{
//		std::cout << "_year:" << _year << " " << "_month:" << _month << " " << "_day:" << _day << std::endl;
// 	}
//private:
//	int _year;
//	int _month;
//	int _day;
//};
//class dzh1
//{
//public:
//	dzh1(int x = 0, int y = 0)//创建默认构造函数与拷贝构造函数做对比
//	{
//		_x = x;
//		_y = y;
//	}
//	//由于没有开辟空间的变量，也就不需要创建析构函数和拷贝构造函数了
//	void Print()
//	{
//		std::cout << "_x:" << _x << " " << "_y:" << _y << std::endl;
//		_k.Print();
//	}
//private:
//	int _x;
//	int _y;
//	Data _k;//注意：作为成员变量时，创建对象时会自动调用默认构造，但却不能显式调用构造函数进行初始化
//};
//
//void csprint(dzh1 a)
//{
//	a.Print();
//}
//
//dzh1 cpy(const dzh1 a)
//{
//	return a;
//}
//
//int main()
//{
//	//初始化时将同类变量a的内容拷贝到b中时（其他两种情况也会自动调用，但由于使用的默认拷贝构造，所以无法显式看出，这里就不一一举例了）：
//	dzh1 a(996, 996);
//	a.Print();//_x:996 _y:996\n_year:2025 _month:7 _day:9
//	dzh1 b = a;
//	b.Print();//_x:996 _y:996\n_year:2025 _month:7 _day:9 ->可以发现，此时b的初始化调用的不是全缺省的默认构造函数，而是默认的拷贝构造
//	/*dzh1 c;
//	c = a;
//	c.Print();但需要注意，拷贝构造也是初始化时才能使用的，像这里的变量c，初始化结束后才使用的赋值符号并非调用了拷贝构造，而是默认的赋值运算符重载*/
//	return 0;
//}


//含动态开辟空间变量时，需创建拷贝构造（只需要处理动态开辟空间的变量即可，其他变量会按照成员顺序自动处理）示例：
class Data
{
public:
	Data(int year = 2025, int month = 7, int day = 9)//创建默认构造函数与拷贝构造函数做对比
	{
		printf("调用了Data的默认构造函数!\n");
		_year = year;
		_month = month;
		_day = day;
	}
	//由于没有开辟空间的变量，也就不需要创建析构函数和拷贝构造函数了
	void Print()
	{
		std::cout << "_year:" << _year << " " << "_month:" << _month << " " << "_day:" << _day << std::endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
class dzh1
{
public:
	//默认构造函数
	dzh1(int x = 0, int y = 0, int n = 4)
	{
		printf("调用了dzh1的默认构造函数!\n");
		_x = x;
		_y = y;
		_n = n;
		_a = (int*)malloc(sizeof(int) * _n);
		assert(_a);
		for (int i = 0; i < _n; i++)
		{
			_a[i] = i;
		}
	}
	//拷贝构造函数
	//dzh1(dzh1 a)//参数中类型为dzh1则报错（拷贝构造的参数不能为类型值，否则会在语法上死递归，实际编译器会直接报错）
	dzh1(const dzh1& a)//正确写法是参数改为引用类型
	{
		printf("调用了dzh1的拷贝构造！\n");
		_x = a._x;
		_y = a._y;
		_n = a._n;
		_a = (int*)malloc(sizeof(int) * _n);//注意：被拷贝的数据做参数，this指针指向的数据是存储变量
		assert(_a);
		for (int i = 0; i < _n; i++)
		{
			_a[i] = a._a[i];
		}
		_k = a._k;//稍超纲，这里用的并非Data的默认拷贝构造，而是Data的默认赋值重载
	}
	//析构函数
	~dzh1()
	{
		printf("调用了dzh1的析构函数!\n");
		free(_a);
		_a = nullptr;
	}
	void Print()
	{
		std::cout << "_x:" << _x << " " << "_y:" << _y << std::endl;
		for (int i = 0; i < _n; i++)
		{
			printf("%d ", _a[i]);
		}
		printf("\n");
		_k.Print();
	}
private:
	int _x;
	int _y;
	int _n;
	int* _a = nullptr;
	Data _k;//注意：作为成员变量时，创建对象时会自动调用默认构造，但却不能显式调用构造函数进行初始化
};

void csprint(dzh1 a)
{
	a.Print();
}

dzh1 cpy(const dzh1& a)
{
	return a;
}

int main()
{
	//初始化时将同类变量a的内容拷贝到b中时（其他两种情况也会自动调用，但由于使用的默认拷贝构造，所以无法显式看出，这里就不一一举例了）：
	printf("a:\n");
	dzh1 a(996, 996);//调用了Data的默认构造函数!
	                 //调用了dzh1的默认构造函数!

	a.Print();
	printf("\nb:\n");
	dzh1 b = a; //调用了Data的默认构造函数!
		        //调用了dzh1的拷贝构造！

	b.Print();//_x:996 _y:996\n_year:2025 _month:7 _day:9 ->可以发现，此时b的初始化调用的不是全缺省的默认构造函数，而是默认的拷贝构造
	
	//函数传值自动调用拷贝构造举例：
	printf("\ncsprint(a):\n");
	csprint(a);//调用了Data的默认构造函数!
	           //调用了dzh1的拷贝构造！
			   //调用了dzh1的析构函数!

	//函数返回值时自动调用拷贝构造：
	printf("\ncpy(a):\n");
	cpy(a);//调用了Data的默认构造函数!
	       //调用了dzh1的拷贝构造！
		   //调用了dzh1的析构函数!

	//析构所有还未释放的空间
	printf("\n析构所有还未释放的空间:\n");
	//调用了dzh1的析构函数!
	//调用了dzh1的析构函数!
	return 0;
}
//通过上述代码调试，可以发现，对象进行初始化的顺序是，先进行类内成员变量声明时的初始化，然后调用成员变量中自定义类型变量的默认构造，最后再调用该类本身的拷贝构造/默认构造
//但通过AI（腾讯元宝）查询，似乎是说调用拷贝构造不会进行类内初始化（c++标准 + release版本时），但经过我使用VS2022和Dev C++两个编译环境的release和debug分别测试时，
//发现显示调用拷贝构造时都会先进行类内初始化，而deepseek的说法与我的实验结果相同，基本能确定第一个ai说的是错的

//*****下列内容可以不看，是我自己的理解，与上面的内容有重复***********************
//若没有显示构建拷贝构造函数，则会默认生成一个，默认生成的会拷贝内置类型，对于自定义类型，会调用该类型的拷贝构造
//但都是浅拷贝（浅拷贝即按字节拷贝，举例来说，若遇到int* a，则会拷贝a的值，即拷贝出相同的地址，会导致函数内形参的改变同时会改变实参）
//因此，对于需要开辟空间的类型，就需要进行深拷贝（深拷贝即不拷贝地址，而是开辟相同的空间，将实参的空间中的内容进行拷贝）

//上面也提到了拷贝构造的自动调用的特点，由于该特点对于所有函数都适用（包括自己），就会出现一些问题，
//比如说，若将拷贝构造函数的参数类型改为自定义类型，在调用拷贝构造时，就会在调用拷贝构造，然后再调用，如此，便会进入死递归（语法层面来说，实际上，编译器会检测拷贝构造的参数，若为值，会直接报错）
//同时，若一个函数的返回值为自定义类型，return会自动调用拷贝构造，若return的是出了函数就销毁的变量，那也没错，但如果是不被销毁的（如静态区，堆区）变量
//则会造成一次无用拷贝，影响程序的执行效率，对于这种情况，就可以采取return const T&/const T&来进行优化
